Building a Flask App for Sentiment Analysis

In this article, we will walk through the process of building a Flask web application that allows users to input text and receive a sentiment prediction based on the entered text. The core functionality of this app relies on a pre-trained machine learning model that predicts sentiment based on the processed input.

Overview

The Flask app consists of two main components:

  1. The Web Interface: A simple web page where users can input text and view the predicted sentiment.
  2. The Backend: This includes the Flask routes and the machine learning model that processes the input and returns the sentiment prediction.

Let's dive into the details of each component.

The Web Interface

The web interface is built using Flask's render_template function, which allows us to render HTML templates. In our app, we have a simple form where users can input text. Once the form is submitted, the text is sent to the Flask backend for processing.

Here's the main route that handles the web interface:

@app.route('/', methods=['GET', 'POST'])
def home():
    predictions=None
    if request.method == 'POST':
        newtext = request.form['text']
        print(newtext)
        # Need to add the preprocessing stuff here
        preprocessed_text = preprocess_text(newtext)
        predictions = predict_personality(preprocessed_text)
    return render_template('index.html', predictions=predictions)

In the above code, when a POST request is made (i.e., when the form is submitted), the entered text is retrieved, preprocessed, and then passed to the machine learning model for sentiment prediction. The predicted sentiment is then displayed on the web page.

<!DOCTYPE html>
<html>
<head>
    <title>Personality Traits Model</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-dark-mode/1.0.2/bootstrap-dark-mode.min.css" integrity="sha512-UNEUxQFkDUwrOl+iyS1pjp1am+X9s30WzT3VHFCtovNnBZCy19hCg42GtT9b1PYfS+Eo7cWX3Aspi6XwML4VhQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />

    <style>
        /* Add your custom styles here */
body{
    background-color:black;
    color:white;
}
    </style>
</head>
<body>
    <div class="container-fluid">
        <div class="row">
            <div class="col-md-6">
    <div class="container">
        <h1 class="text-center my-4">Improving Personality Trait Prediction Models</h1>
        <div class="my-4">
            <h2>1. Feature Selection</h2>
            <p>Feature selection is the process of reducing the number of input variables when developing a predictive model. It is desirable to reduce the number of input variables to both reduce the computational cost of modeling and, in some cases, to improve the performance of the model. <a href='https://en.wikipedia.org/wiki/Feature_selection'>Reference</a></p>
            <h2>2. Hyperparameter Tuning</h2>
            <p>Hyperparameters are parameters whose values are used to control the learning process. By contrast, the values of other parameters (typically node weights) are learned. The same kind of machine learning model can require different constraints, weights or learning rates to generalize different data patterns. These measures are called hyperparameters, and have to be tuned so that the model can optimally solve the machine learning problem. <a href='https://en.wikipedia.org/wiki/Hyperparameter_(machine_learning)'>Reference</a></p>
            <h2>3. Ensemble Methods</h2>
            <p>In statistics and machine learning, ensemble methods use multiple learning algorithms to obtain better predictive performance than could be obtained from any of the constituent learning algorithms alone. <a href='https://en.wikipedia.org/wiki/Ensemble_learning'>Reference</a></p>
            <h2>4. Cross Validation</h2>
            <p>Cross-validation, sometimes called rotation estimation or out-of-sample testing, is any of various similar model validation techniques for assessing how the results of a statistical analysis will generalize to an independent data set. <a href='https://en.wikipedia.org/wiki/Cross-validation_(statistics)'>Reference</a></p>
            <h2>5. Linear Discriminant Analysis (LDA)</h2>
            <p>Linear discriminant analysis (LDA) is a generalization of Fisher's linear discriminant, a method used in statistics, pattern recognition, and machine learning to find a linear combination of features that characterizes or separates two or more classes of objects or events. The resulting combination may be used as a linear classifier, or, more commonly, for dimensionality reduction before later classification. <a href='https://en.wikipedia.org/wiki/Linear_discriminant_analysis'>Reference</a></p>
        </div>
    </div>
                <!-- Article content goes here -->
            </div><!-- end of first column-->
            <div class="col-md-6">
                <div class="row">
                    <div class="col-md-12">
    <div class="container py-5">
        <div class="row">
            <div class="col-md-6 mx-auto">
                <div class="card">
                    <div class="card-body">
                        <form method="POST">
                            <div class="form-group">
                                <label for="text">Enter text:</label>
                                <input type="text" id="text" name="text" class="form-control">
                            </div>
                            <input type="submit" value="Submit" class="btn btn-primary btn-block">
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>
                        <!-- Form for entering text goes here -->
                    </div>
                </div>
                <div class="row">
                    <div class="col-md-12">
                        <!-- Area for displaying predictions goes here -->
    <h2>Predictions</h2>
    {% if predictions is not none %}

    {% for trait, score in predictions.items() %}
        <p>{{ trait }}: {{ score }}</p>
    {% endfor %}
    {% endif %}

                    </div>
                </div> <!-- end of second column second row-->
            </div> <!-- end of second column -->
        </div>
    </div>
</body>
</html>

#!pip install nltk
#!pip install flask
#!pip install joblib
#!pip install pandas
#!pip install numpy
#!pip install -U scikit-learn

The Simple Flask API

Here we create a simple flask application with templates (recall that these will require their own directory for the application to function properly).

from flask import Flask, request, render_template
from model import preprocess_text, predict_personality
import joblib

# Initialize Flask app
app = Flask(__name__)
@app.route('/', methods=['GET', 'POST'])
def home():
    predictions=None
    if request.method == 'POST':
        newtext = request.form['text']
        print(newtext)
        # Need to add the preprocessing stuff here

        preprocessed_text = preprocess_text(newtext)
        predictions = predict_personality(preprocessed_text)
    return render_template('index.html', predictions=predictions)

if __name__ == '__main__':
    app.run(debug=True)

The Jinja Template

Jinja is a modern and designer-friendly templating engine for Python programming languages. It's commonly used with Flask to render web pages. In our Flask app, the render_template function is used to render the Jinja template, allowing dynamic content to be displayed based on user input and the app's logic.

Structure of the Template

While the exact content of the Jinja template you added isn't shown here, a typical template for our Flask app might include:

  • An HTML form that allows users to input text.
  • Placeholders for displaying dynamic content, such as the predicted sentiment. In Jinja, these placeholders are denoted by double curly braces, like {{ predictions }}.
  • Conditional statements or loops to handle different scenarios or display lists of data.

Integration with Flask

The Flask route retrieves data from the Jinja template using the request.form method. Once the sentiment prediction is made, the result is passed back to the template using the render_template function. The dynamic content in the template (e.g., {{ predictions }}) gets replaced with the actual data provided by the Flask app.

Using Jinja templates with Flask provides a seamless way to create interactive web applications with dynamic content, making it a popular choice for web developers and data scientists alike.

The model Development (backend)

The Backend

The backend of our Flask app is where the main logic resides. This includes preprocessing the input text and using a machine learning model to predict sentiment.

Preprocessing the Text

Before passing the input text to the machine learning model, it's essential to preprocess it. This ensures that the text is in a format that the model expects. The preprocess_text function is responsible for this task. While the exact preprocessing steps might vary based on the model and dataset used, common steps include tokenization, removing stop words, and stemming.

Predicting Sentiment

Once the text is preprocessed, it's passed to the predict_personality function, which uses a pre-trained machine learning model to predict sentiment. The model and any necessary assets (like a vectorizer) are typically loaded using the joblib library.

In the provided code, the exact implementation details of preprocess_text and predict_personality are not shown as they are part of the model.py file. However, these functions play a crucial role in the app's functionality.

Detailed Overview of the Sentiment Analysis Model

For the sentiment analysis task in our Flask app, we utilized a dictionary of Linear Regression models. Each trait in our dataset has its own dedicated Linear Regression model, allowing for a more granular prediction of sentiment based on different traits.

Linear Regression

Linear Regression is a linear approach to modeling the relationship between a dependent variable and one or more independent variables. In the context of our app, the dependent variable is the sentiment score for a particular trait, and the independent variables are the features extracted from the input text.

The model is represented as:

$$y = \beta_0 + \beta_1 x_1 + \beta_2 x_2 + ... + \beta_n x_n $$

Where:

  • $ y $ is the predicted sentiment score.
  • $ \beta_0 $ is the intercept.
  • $ \beta_1, \beta_2, ... $are the coefficients of the independent variables.
  • $x_1, x_2, ... $ are the independent variables (features extracted from the text).

Text Preprocessing with NLTK

Before feeding the text into the model, it undergoes several preprocessing steps using the Natural Language Toolkit (NLTK). Common preprocessing steps include:

  • Tokenization: Splitting the text into individual words or tokens.
  • Stopword Removal: Removing common words (e.g., 'and', 'the', 'is') that don't add significant meaning in the context of sentiment analysis.
  • Stemming: Reducing words to their base or root form (e.g., 'running' becomes 'run').

Model Creation

The code snippet models = {trait: LinearRegression().fit(X, df[trait]) for trait in traits} indicates that for each trait in the dataset, a separate Linear Regression model is trained. The input X is the vectorized representation of the text after preprocessing, and df[trait] is the sentiment score for that particular trait.

In the next cell, we'll provide a simple demonstration using a sample Linear Regression model.

#The next file is model.py



import pandas as pd
import numpy as np
import random
import nltk
import re
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LinearRegression
from nltk.corpus import stopwords
from nltk.tokenize import word_tokenize

from joblib import dump,load
# The actual object dumps are at the end of the file so look down there.



# Define seeds and objects, corpus etc
n_samples = 100
np.random.seed(42)
words = ['I', 'love', 'exploring', 'new', 'places', 'trying', 'things',
         'am', 'organized', 'person', 'like', 'to', 'keep', 'things', 'in', 'order',
         'enjoy', 'going', 'out', 'with', 'friends', 'spend', 'lot', 'of', 'time', 'alone',
         'am', 'always', 'nice', 'to', 'people', 'feel', 'anxious', 'often']
traits = ['openness', 'conscientiousness', 'extraversion', 'agreeableness', 'neuroticism']
#key part right here.

texts = [' '.join(random.choices(words, k=random.randint(4, 10))) for _ in range(n_samples)]


#### From Distribution
openness = np.random.uniform(low=1, high=5, size=n_samples)
conscientiousness = np.random.uniform(low=1, high=5, size=n_samples)
extraversion = np.random.uniform(low=1, high=5, size=n_samples)
agreeableness = np.random.uniform(low=1, high=5, size=n_samples)
neuroticism = np.random.uniform(low=1, high=5, size=n_samples)
#### From Distribution


df = pd.DataFrame({
    'text': texts,
    'openness': openness,
    'conscientiousness': conscientiousness,
    'extraversion': extraversion,
    'agreeableness': agreeableness,
    'neuroticism': neuroticism
})

def preprocess_text(text):
    text = text.lower()
    text = re.sub(r'[^\w\s]', '', text)
    words = word_tokenize(text)
    words = [word for word in words if word not in stopwords.words('english')]
    return ' '.join(words)
nltk.download('stopwords')
nltk.download('punkt')

df['text'] = df['text'].apply(preprocess_text)
vectorizer = TfidfVectorizer()
X = vectorizer.fit_transform(df['text'])
models = {trait: LinearRegression().fit(X, df[trait]) for trait in traits}



def writeModels():
    # dataframe is global so we can refer to it here.
    df['text'] = df['text'].apply(preprocess_text)
    vectorizer = TfidfVectorizer()
    X = vectorizer.fit_transform(df['text'])
    models = {trait: LinearRegression().fit(X, df[trait]) for trait in traits}
    return models

def preprocess_text(text):
    text = text.lower()
    text = re.sub(r'[^\w\s]', '', text)
    words = word_tokenize(text)
    words = [word for word in words if word not in stopwords.words('english')]
    return ' '.join(words)


### Testing/applying the ml

new_text = ["I love exploring new places and trying new things."]
preprocessed_text = [preprocess_text(text) for text in new_text]
X_new = vectorizer.transform(preprocessed_text)
# dumpting the models
dump(models, 'models.joblib')
dump(vectorizer, 'vectorizer.joblib')

# some predictions
predictions = {trait: model.predict(X_new)[0] for trait, model in models.items()}
### Testing/applying the ml END
print(predictions)

def somePlots():
    for i, trait in enumerate(traits):
        plt.figure(figsize=(8,6))
        sns.histplot(df[trait], kde=True, bins=30)
        plt.title(f'Distribution of {trait}')
        plt.savefig(f'hist_{trait}.png')  # Save the figure before plt.show()
        plt.show()


def showmodelpandI():
    corr = df[traits].corr()
    mask = np.triu(np.ones_like(corr, dtype=bool))
    f, ax = plt.subplots(figsize=(11, 9))
    cmap = sns.diverging_palette(230, 20, as_cmap=True)
    sns.heatmap(corr, mask=mask, cmap=cmap, vmax=.3, center=0,
                square=True, linewidths=.5, cbar_kws={"shrink": .5})
    plt.savefig('heatmap.png')  # Save the figure before plt.show()

def predict_personality(text):
    '''
    Make sure that wherever you want to run this, that you have the vectorizer
    and models therein
    '''
    # load models and vectorize
    models=load('models.joblib')
    vectorizer = load('vectorizer.joblib')

    # Preprocess the text
    preprocessed_text = preprocess_text(text)

    # Transform the text to its TF-IDF representation
    X_new = vectorizer.transform([preprocessed_text])

    # Use the trained models to predict the personality traits
    predictions = {trait: model.predict(X_new)[0] for trait, model in models.items()}

    return predictions
{'openness': 3.0791979480525598, 'conscientiousness': 1.6925162419745827, 'extraversion': 2.9076673759393037, 'agreeableness': 4.397176392281509, 'neuroticism': 3.3299947962026137}

Conclusion

Building a Flask web application for sentiment analysis allows users to easily interact with machine learning models without needing to understand the underlying complexities. By integrating a user-friendly web interface with a robust backend, we can provide users with valuable insights based on their input.

While the provided code offers a basic implementation, there are numerous ways to enhance and expand the app. This includes improving the machine learning model, adding more features to the web interface, and integrating with other services or databases.

If you're interested in diving deeper into Flask or sentiment analysis, there are plenty of resources available online to guide you through more advanced topics and implementations.

Understanding the Sentiment Analysis Model

Sentiment analysis, often referred to as opinion mining, involves determining the sentiment or emotion expressed in a piece of text. In the context of our Flask app, the sentiment analysis model predicts the sentiment of the user's input.

Model Architecture

While the exact architecture of the model used in the Flask app isn't provided, common architectures for sentiment analysis include:

  • Logistic Regression: A simple linear model that can be used for binary classification tasks.
  • Neural Networks: More complex models that can capture intricate patterns in the data. This includes architectures like RNNs, LSTMs, and Transformers.
  • BERT (Bidirectional Encoder Representations from Transformers): A state-of-the-art model that has achieved remarkable results in various NLP tasks, including sentiment analysis.

Model Limitations

Every machine learning model has its limitations. For sentiment analysis, some potential limitations include:

  • Ambiguity: Text with ambiguous sentiment might be challenging to classify correctly.
  • Sarcasm: Detecting sarcasm is notoriously difficult for algorithms.
  • Domain-Specific Language: The model might struggle with text from domains it wasn't trained on.
  • Short Texts: Very short texts might not provide enough context for accurate sentiment prediction.

Model Demonstration

In the next cell, we'll provide a simple demonstration of how the model might work, including some visualizations.

import matplotlib.pyplot as plt
import numpy as np

# Sample data for demonstration purposes
sentiments = ['Positive', 'Negative', 'Neutral']
values = [0.6, 0.3, 0.1]

# Plotting a pie chart to demonstrate sentiment distribution
plt.figure(figsize=(8, 6))
plt.pie(values, labels=sentiments, autopct='%1.1f%%', startangle=140, colors=['green', 'red', 'gray'])
plt.title('Sample Sentiment Distribution')
plt.show()
 
from sklearn.linear_model import LinearRegression
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

# Generate sample data for demonstration
X_demo, y_demo = make_regression(n_samples=100, n_features=1, noise=15, random_state=42)
X_train, X_test, y_train, y_test = train_test_split(X_demo, y_demo, test_size=0.2, random_state=42)

# Train a sample Linear Regression model
regressor = LinearRegression().fit(X_train, y_train)
y_pred = regressor.predict(X_test)

# Plotting the regression line
plt.figure(figsize=(8, 6))
plt.scatter(X_test, y_test, color='blue', label='Actual Values')
plt.plot(X_test, y_pred, color='red', label='Regression Line')
plt.title('Linear Regression Demonstration')
plt.xlabel('Feature Value')
plt.ylabel('Target Value')
plt.legend()
plt.show()

# Calculate and display the mean squared error
mse = mean_squared_error(y_test, y_pred)
mse

Model Evaluation Metrics

Evaluating the performance of a machine learning model is crucial to understand its effectiveness and identify areas for improvement. For regression tasks like the one in our Flask app, several metrics can be used to assess the model's accuracy and precision.

In this section, we'll calculate and discuss some common evaluation metrics using a sample dataset.

from sklearn.metrics import mean_absolute_error, r2_score

# Calculate evaluation metrics
mae = mean_absolute_error(y_test, y_pred)
r2 = r2_score(y_test, y_pred)

mae, r2

Future Directions

While the current implementation of the Flask app provides valuable insights into sentiment analysis, there are several potential directions for future enhancement and expansion:

  1. Model Improvement: Experiment with more advanced machine learning models or architectures, such as deep neural networks or transformer-based models like BERT, to potentially improve prediction accuracy.

  2. Data Augmentation: Increase the diversity and size of the training dataset by using techniques like data augmentation. This can help the model generalize better to unseen data.

  3. User Feedback Loop: Implement a feedback mechanism where users can provide feedback on the model's predictions. This feedback can be used to further train and refine the model.

  4. Integration with Other Services: Enhance the app by integrating with other services or databases, allowing for richer user interactions and more comprehensive insights.

  5. Expand to Multimodal Analysis: Incorporate other forms of data, such as audio or video, to perform multimodal sentiment analysis.

  6. Deployment and Scaling: Consider deploying the app on cloud platforms and using technologies like Docker or Kubernetes to ensure scalability and handle large numbers of users.

By exploring these directions, the Flask app can be transformed into a more robust and versatile tool for sentiment analysis and other related tasks.